package org.jzyan.framework.core.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;

/**
 * 国密对称加密SM4算法工具类，用于数据库存储加密
 *
 * @author whj, 20200804
 */
public class SM4Utils {

    private static final String PUBLIC_KEY = "UMPAY/COMMON/SYMM/2020==";

    private static final Logger logger = LoggerFactory.getLogger(SM4Utils.class);

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final String ALGORITHM_NAME = "SM4";
    private static final String DEFAULT_IV = "UMPAYBJ_20030815";
    private static final int DEFAULT_KEY_SIZE = 128;
    private static final String DEFAULT_ENCODING = "UTF-8";

    /**
     * 自动生成密钥
     *
     * @return Base64编码的密钥
     */
    public static String generateBase64Key() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(DEFAULT_KEY_SIZE, new SecureRandom());
        byte[] bytes = kg.generateKey().getEncoded();
        return Base64.toBase64String(bytes);
    }

    /**
     * sm4加密
     *
     * @param content   待加密字符串
     * @param publicKey 密钥
     * @return 返回Base64编码的加密字符串
     * 密文长度不固定，会随着被加密字符串长度的变化而变化
     */
    public static String encrypt(String content, String publicKey) {
        String cipherText = null;
        try {
            byte[] keyData;
            if (ObjectUtils.isEmpty(publicKey)) {
                keyData = Base64.decode(PUBLIC_KEY);
            } else {
                keyData = Base64.decode(publicKey);
            }
            byte[] srcData = content.getBytes(DEFAULT_ENCODING);
            byte[] cipherArray = encrypt(keyData, srcData);
            logger.info("cipherArray-lenth=" + cipherArray.length);
            cipherText = Base64.toBase64String(cipherArray);
        } catch (Exception e) {
            logger.error("encrypt error!!!", e);

        }
        return cipherText;
    }

    /**
     * sm4解密
     *
     * @param cipherText Base64编码的加密字符串（忽略大小写）
     * @param publicKey  密钥
     * @return 解密后的字符串
     */
    public static String decrypt(String cipherText, String publicKey) {
        String decryptStr = null;
        byte[] keyData;
        if (ObjectUtils.isEmpty(publicKey)) {
            keyData = Base64.decode(PUBLIC_KEY);
        } else {
            keyData = Base64.decode(publicKey);
        }
        byte[] cipherData = Base64.decode(cipherText);
        byte[] srcData;
        try {
            srcData = decrypt(keyData, cipherData);
            decryptStr = new String(srcData, DEFAULT_ENCODING);
        } catch (Exception e) {
            logger.error("decrypt error!!!", e);
        }
        return decryptStr;
    }

    private static byte[] encrypt(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateCipher(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    private static byte[] decrypt(byte[] key, byte[] cipherText) throws Exception {
        Cipher cipher = generateCipher(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    private static Cipher generateCipher(int mode, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(SM4ModeAndPaddingEnum.SM4_CBC_PKCS5Padding.getName(), BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        IvParameterSpec ivParameterSpec = null;
        if (DEFAULT_IV != null) {
            ivParameterSpec = new IvParameterSpec(DEFAULT_IV.getBytes("UTF-8"));
        }
        cipher.init(mode, sm4Key, ivParameterSpec);
        return cipher;
    }

    /**
     * SM4对称加解密算法中的模式和padding方式枚举类
     */
    public enum SM4ModeAndPaddingEnum {
        SM4_ECB_NoPadding("SM4/ECB/NoPadding"),
        SM4_ECB_PKCS5Padding("SM4/ECB/PKCS5Padding"),
        SM4_ECB_PKCS7Padding("SM4/ECB/PKCS7Padding"),
        SM4_CBC_NoPadding("SM4/CBC/NoPadding"),
        SM4_CBC_PKCS5Padding("SM4/CBC/PKCS5Padding"),
        SM4_CBC_PKCS7Padding("SM4/CBC/PKCS7Padding");
        private String name;

        SM4ModeAndPaddingEnum(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

}


